﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using Nvigorate.Aspect;
using Nvigorate.Common;
using Nvigorate.Relational.Mapping;
using Nvigorate.Relational.Mapping.Interface;
using Nvigorate.Relational.State;

namespace Nvigorate.Relational.Repository
{
    public interface IPersistable : IPersistenceState
    {
        /// <summary>
        /// The instance to be persisted
        /// </summary>
        object Instance { get; set; }

        /// <summary>
        /// The type of the persisted instance
        /// </summary>
        Type InstanceType { get; }

        /// <summary>
        /// A unique identifier for this instance
        /// The implementation should borrow from the AddIdentityAspect
        /// </summary>
        Guid PersistenceTicket { get; }

        /// <summary>
        /// A list of all instances which contribute foreign key values
        /// and their relationship to this instance
        /// </summary>
        List<Tuple<ITargetRelationship, IPersistable>> Relatives { get; }

        /// <summary>
        /// Returns the parent's PersistenceTicket given the property
        /// which represents the foreign key
        /// </summary>
        /// <param name="relativeType"></param>
        /// <param name="foreignKeyProperty"></param>
        /// <returns></returns>
        Guid GetRelativeTicket(Type relativeType, string foreignKeyProperty);
    }

    public class PersistenceDecorator : IPersistable
    {
        protected Guid _persistenceTicket = Guid.Empty;
        protected object _instance;
        protected IPersistenceState _trackingState;
        protected List<Tuple<ITargetRelationship, IPersistable>> _relatives = new List<Tuple<ITargetRelationship, IPersistable>>();

        public event PropertyChangedEventHandler PropertyChanged;
        public event PropertyChangingEventHandler PropertyChanging;

        public List<Tuple<ITargetRelationship, IPersistable>> Relatives
        {
            get { return _relatives; }
        }

        public Guid PersistenceTicket
        {
            get
            {
                var identifiable = _instance as IIdentifiable;
                if(identifiable != null)
                {
                    _persistenceTicket = (Guid) identifiable.InstanceId;
                }
                else
                {
                    _persistenceTicket = Guid.NewGuid();
                }
                return _persistenceTicket;
            }
        }

        public object Instance
        {
            get { return _instance; }
            set { _instance = value; }
        }

        public Type InstanceType
        {
            get { return _instance.GetType(); }
        }

        #region Implementation of ITrackState

        public List<Tuple<string, object>> ChangedValues
        {
            get { return _trackingState.ChangedValues; }
            set { _trackingState.ChangedValues = value; }
        }

        public bool IsDirty
        {
            get { return _trackingState.IsDirty; }
        }

        public bool IsNew
        {
            get { return _trackingState.IsNew; }
        }

        public List<string> Keys
        {
            get { return _trackingState.Keys; }
            set { _trackingState.Keys = value; }
        }

        public List<string> Loaded
        {
            get { return _trackingState.Loaded; }
            set { _trackingState.Loaded = value; }
        }

        public bool RelationshipChanged
        {
            get { return _trackingState.RelationshipChanged; }
            set { _trackingState.RelationshipChanged = value; }
        }

        public string RepositoryName
        {
            get { return _trackingState.RepositoryName; }
            set { _trackingState.RepositoryName = value; }
        }

        public void AddChangedValue(string changedPropertyName, object originalValue)
        {
            _trackingState.AddChangedValue(changedPropertyName, originalValue);
        }

        public object GetOriginalValue(string propertyName, object instance)
        {
            return _trackingState.GetOriginalValue(propertyName, _instance);
        }

        public void ClearDirtyState()
        {
            _trackingState.ClearDirtyState();
        }

        public void ForceInsert()
        {
            _trackingState.ForceInsert();
        }

        #endregion

        public Guid GetRelativeTicket(Type relativeType, string foreignKeyProperty)
        {
            var parent = _relatives
                .FirstOrDefault(p =>
                {
                    if (p.Value1 is OneToManyRelationship)
                    {
                        var oneToMany = p.Value1 as OneToManyRelationship;
                        return (oneToMany.Constraints.ContainsValue(foreignKeyProperty) && oneToMany.RelativeType == relativeType) ||
                               (oneToMany.Constraints.ContainsKey(foreignKeyProperty) && oneToMany.SubjectType == relativeType);
                    }
                    else
                    {
                        var manyToMany = p.Value1 as ManyToManyRelationship;
                        return (manyToMany.Container.SubjectPairs.Any(s => s.Target == foreignKeyProperty) && manyToMany.SubjectType == relativeType) ||
                               (manyToMany.Container.RelativePairs.Any(r => r.Target == foreignKeyProperty) && manyToMany.RelativeType == relativeType);
                    }
                });

            return parent == null ? PersistenceTicket : parent.Value2.PersistenceTicket;
        }

        public void RaisePropertyChanged(string property)
        {
            if(PropertyChanged != null)
                PropertyChanged(this, new PropertyChangedEventArgs(property));
        }

        public void RaisePropertyChanging(string property)
        {
            if(PropertyChanging != null)
                PropertyChanging(this, new PropertyChangingEventArgs(property));
        }

        public static IPersistable Create(object instance)
        {
            return new PersistenceDecorator
                    {
                        Instance = instance,
                        _trackingState = instance is ITrackState ? (instance as ITrackState).InstanceState : CreateDefaultStateTrackingForInstance(instance)
                    };
        }

        protected static IPersistenceState CreateDefaultStateTrackingForInstance(object instance)
        {
            var information = new StateInformation();

            return information;
        }
    }
}